﻿' Copyright (c) Microsoft Open Technologies, Inc.  All Rights Reserved.  Licensed under the Apache License, Version 2.0.  See License.txt in the project root for license information.

Imports System.Runtime.CompilerServices
Imports System.Runtime.InteropServices
Imports InternalSyntaxFactory = Microsoft.CodeAnalysis.VisualBasic.Syntax.InternalSyntax.SyntaxFactory

'
'============ Methods for parsing portions of executable statements ==
'

Namespace Microsoft.CodeAnalysis.VisualBasic.Syntax.InternalSyntax

    Partial Class Parser

        Private Function ParseInterpolatedStringExpression() As InterpolatedStringExpressionSyntax
            Debug.Assert(CurrentToken.Kind = SyntaxKind.DollarSignDoubleQuoteToken, "ParseInterpolatedStringExpression called on the wrong token.")

            ResetCurrentToken(ScannerState.InterpolatedStringPunctuation)

            Debug.Assert(CurrentToken.Kind = SyntaxKind.DollarSignDoubleQuoteToken, "Rescanning $"" failed.")

            Dim dollarSignDoubleQuoteToken = DirectCast(CurrentToken, PunctuationSyntax)
            GetNextToken(ScannerState.InterpolatedStringContent)

            Dim contentBuilder = _pool.Allocate(Of InterpolatedStringContentSyntax)

            Dim doubleQuoteToken As PunctuationSyntax
            Dim skipped As SyntaxListBuilder(Of SyntaxToken) = Nothing
            Do
                Dim content As InterpolatedStringContentSyntax

                Select Case CurrentToken.Kind
                    Case SyntaxKind.InterpolatedStringTextToken

                        Dim textToken = DirectCast(CurrentToken, InterpolatedStringTextTokenSyntax)

                        ' At this point we're either right before an {, an } (error), or a "".
                        GetNextToken(ScannerState.InterpolatedStringPunctuation)

                        Debug.Assert(CurrentToken.Kind <> SyntaxKind.InterpolatedStringTextToken,
                                     "Two interpolated string text literal tokens encountered back-to-back. " &
                                     "Scanner should have scanned these as a single token.")

                        content = SyntaxFactory.InterpolatedStringText(textToken)

                    Case SyntaxKind.OpenBraceToken

                        content = ParseInterpolatedStringInterpolation()

                    Case SyntaxKind.CloseBraceToken

                        If skipped.IsNull Then
                            skipped = _pool.Allocate(Of SyntaxToken)
                        End If

                        skipped.Add(CurrentToken)
                        GetNextToken(ScannerState.InterpolatedStringContent)
                        Continue Do

                    Case SyntaxKind.DoubleQuoteToken

                        doubleQuoteToken = DirectCast(CurrentToken, PunctuationSyntax)
                        GetNextToken()
                        Exit Do

                    Case SyntaxKind.EndOfInterpolatedStringToken

                        doubleQuoteToken = InternalSyntaxFactory.MissingPunctuation(SyntaxKind.DoubleQuoteToken)
                        GetNextToken(ScannerState.VB)
                        Exit Do

                    Case Else

                        doubleQuoteToken = InternalSyntaxFactory.MissingPunctuation(SyntaxKind.DoubleQuoteToken)
                        Exit Do

                End Select

                If Not skipped.IsNull Then
                    content = AddLeadingSyntax(content, _pool.ToListAndFree(skipped), ERRID.ERR_Syntax)
                    skipped = Nothing
                End If

                contentBuilder.Add(content)
            Loop

            If Not skipped.IsNull Then
                doubleQuoteToken = AddLeadingSyntax(doubleQuoteToken, _pool.ToListAndFree(skipped), ERRID.ERR_Syntax)
                skipped = Nothing
            End If

            Return SyntaxFactory.InterpolatedStringExpression(dollarSignDoubleQuoteToken,
                                                              _pool.ToListAndFree(contentBuilder),
                                                              doubleQuoteToken)

        End Function

        Private Function ParseInterpolatedStringInterpolation() As InterpolationSyntax
            Debug.Assert(CurrentToken.Kind = SyntaxKind.OpenBraceToken, "ParseInterpolatedStringEmbeddedExpression called on the wrong token.")

            Dim colonToken As PunctuationSyntax = Nothing
            Dim excessText As String = Nothing

            Dim openBraceToken = DirectCast(CurrentToken, PunctuationSyntax)
            GetNextToken(ScannerState.VB)

            Dim expression As ExpressionSyntax

            If CurrentToken.Kind = SyntaxKind.ColonToken Then

                openBraceToken = DirectCast(RemoveTrailingColonTriviaAndConvertToColonToken(openBraceToken, colonToken, excessText), PunctuationSyntax)
                expression = ReportSyntaxError(InternalSyntaxFactory.MissingExpression(), ERRID.ERR_ExpectedExpression)

            Else
                expression = ParseExpression()

                ' Scanned this as a terminator. Fix it.
                If CurrentToken.Kind = SyntaxKind.ColonToken Then
                    expression = DirectCast(RemoveTrailingColonTriviaAndConvertToColonToken(expression, colonToken, excessText), ExpressionSyntax)
                End If
            End If

            Dim alignmentClauseOpt As InterpolationAlignmentClauseSyntax

            If CurrentToken.Kind = SyntaxKind.CommaToken Then
                Debug.Assert(colonToken Is Nothing)

                Dim commaToken = DirectCast(CurrentToken, PunctuationSyntax)
                GetNextToken(ScannerState.VB)

                If CurrentToken.Kind = SyntaxKind.ColonToken Then
                    commaToken = DirectCast(RemoveTrailingColonTriviaAndConvertToColonToken(commaToken, colonToken, excessText), PunctuationSyntax)
                End If

                Dim signTokenOpt As PunctuationSyntax

                If CurrentToken.Kind = SyntaxKind.MinusToken OrElse
                   CurrentToken.Kind = SyntaxKind.PlusToken Then

                    signTokenOpt = DirectCast(CurrentToken, PunctuationSyntax)
                    GetNextToken(ScannerState.VB)

                    If CurrentToken.Kind = SyntaxKind.ColonToken Then
                        signTokenOpt = DirectCast(RemoveTrailingColonTriviaAndConvertToColonToken(signTokenOpt, colonToken, excessText), PunctuationSyntax)
                    End If
                Else
                    signTokenOpt = Nothing
                End If

                Dim widthToken As IntegerLiteralTokenSyntax

                If CurrentToken.Kind = SyntaxKind.IntegerLiteralToken Then

                    widthToken = DirectCast(CurrentToken, IntegerLiteralTokenSyntax)
                    GetNextToken(ScannerState.VB)

                    If CurrentToken.Kind = SyntaxKind.ColonToken Then
                        widthToken = DirectCast(RemoveTrailingColonTriviaAndConvertToColonToken(widthToken, colonToken, excessText), IntegerLiteralTokenSyntax)
                    End If

                    If Not widthToken.ContainsDiagnostics AndAlso
                       (widthToken.Base <> LiteralBase.Decimal OrElse widthToken.TypeSuffix <> TypeCharacter.None) _
                    Then
                        widthToken = ReportSyntaxError(widthToken, ERRID.ERR_Syntax)
                    End If
                Else
                    widthToken = ReportSyntaxError(InternalSyntaxFactory.MissingIntegerLiteralToken(), ERRID.ERR_ExpectedIntLiteral)
                End If

                Dim valueExpression As ExpressionSyntax = SyntaxFactory.NumericLiteralExpression(widthToken)

                If signTokenOpt IsNot Nothing Then
                    valueExpression = SyntaxFactory.UnaryExpression(
                                                        If(signTokenOpt.Kind = SyntaxKind.PlusToken, SyntaxKind.UnaryPlusExpression, SyntaxKind.UnaryMinusExpression),
                                                        signTokenOpt,
                                                        valueExpression)
                End If

                alignmentClauseOpt = SyntaxFactory.InterpolationAlignmentClause(commaToken, valueExpression)
            Else
                alignmentClauseOpt = Nothing
            End If

            Dim formatStringClauseOpt As InterpolationFormatClauseSyntax

            If CurrentToken.Kind = SyntaxKind.ColonToken AndAlso colonToken IsNot Nothing Then
                ' If colonToken IsNot Nothing we were able to recovery.

                GetNextToken(ScannerState.InterpolatedStringFormatString)

                Dim formatStringToken As InterpolatedStringTextTokenSyntax

                If CurrentToken.Kind = SyntaxKind.InterpolatedStringTextToken Then
                    formatStringToken = DirectCast(CurrentToken, InterpolatedStringTextTokenSyntax)
                    GetNextToken(ScannerState.InterpolatedStringPunctuation)

                    If excessText IsNot Nothing Then
                        formatStringToken = InternalSyntaxFactory.InterpolatedStringTextToken(excessText & formatStringToken.Text,
                                                                                              excessText & formatStringToken.Value,
                                                                                              formatStringToken.GetLeadingTrivia(),
                                                                                              formatStringToken.GetTrailingTrivia())
                    End If
                Else
                    If excessText IsNot Nothing Then
                        formatStringToken = InternalSyntaxFactory.InterpolatedStringTextToken(excessText,
                                                                                              excessText,
                                                                                              Nothing,
                                                                                              Nothing)
                    Else
                        formatStringToken = Nothing
                    End If
                End If

                If formatStringToken Is Nothing Then
                    formatStringToken = DirectCast(InternalSyntaxFactory.MissingToken(SyntaxKind.InterpolatedStringTextToken), InterpolatedStringTextTokenSyntax)
                    formatStringToken = ReportSyntaxError(formatStringToken, ERRID.ERR_Syntax)

                ElseIf formatStringToken.GetTrailingTrivia() IsNot Nothing Then
                    formatStringToken = ReportSyntaxError(formatStringToken, ERRID.ERR_InterpolationFormatWhitespace)

                End If

                formatStringClauseOpt = SyntaxFactory.InterpolationFormatClause(colonToken, formatStringToken)

            Else
                formatStringClauseOpt = Nothing

                If CurrentToken.Kind = SyntaxKind.ColonToken Then
                    ' But if colonToken is null we weren't able to gracefully recover.
                    GetNextToken(ScannerState.InterpolatedStringFormatString)
                End If
            End If

            Dim closeBraceToken As PunctuationSyntax

            If CurrentToken.Kind = SyntaxKind.CloseBraceToken Then
                ' Must rescan this with interpolated string rules for trailing trivia.
                ' Specifically, any trailing trivia attached to the closing brace is actually interpolated string content.
                ResetCurrentToken(ScannerState.InterpolatedStringPunctuation)

                closeBraceToken = DirectCast(CurrentToken, PunctuationSyntax)
                GetNextToken(ScannerState.InterpolatedStringContent)

            ElseIf CurrentToken.Kind = SyntaxKind.EndOfInterpolatedStringToken
                GetNextToken(ScannerState.VB)

                closeBraceToken = DirectCast(HandleUnexpectedToken(SyntaxKind.CloseBraceToken), PunctuationSyntax)
            Else
                ' Content rules will either resync at a } or at the closing ".
                ResetCurrentToken(ScannerState.InterpolatedStringFormatString)

                Dim skippedToken As SyntaxToken = Nothing

                If CurrentToken.Kind = SyntaxKind.InterpolatedStringText Then
                    skippedToken = CurrentToken
                    GetNextToken(ScannerState.InterpolatedStringPunctuation)
                End If

                If CurrentToken.Kind = SyntaxKind.CloseBraceToken Then
                    closeBraceToken = DirectCast(CurrentToken, PunctuationSyntax)
                    GetNextToken(ScannerState.InterpolatedStringContent)
                Else
                    closeBraceToken = DirectCast(HandleUnexpectedToken(SyntaxKind.CloseBraceToken), PunctuationSyntax)
                End If

                If skippedToken IsNot Nothing Then
                    closeBraceToken = AddLeadingSyntax(closeBraceToken, skippedToken, ERRID.ERR_Syntax)
                End If

            End If

            Return SyntaxFactory.Interpolation(openBraceToken, expression, alignmentClauseOpt, formatStringClauseOpt, closeBraceToken)

        End Function

        Private Shared Function RemoveTrailingColonTriviaAndConvertToColonToken(
                             token As SyntaxToken,
                             <Out> ByRef colonToken As PunctuationSyntax,
                             <Out> ByRef excessText As String
                         ) As SyntaxToken

            If Not token.HasTrailingTrivia Then
                colonToken = Nothing
                excessText = Nothing
                Return token
            End If

            Dim triviaList As New SyntaxList(Of VisualBasicSyntaxNode)(token.GetTrailingTrivia())

            Dim indexOfFirstColon As Integer = -1
            Dim newTrailingTrivia As GreenNode

            If triviaList.Count = 1 Then
                indexOfFirstColon = 0
                newTrailingTrivia = Nothing
                excessText = Nothing
            ElseIf triviaList(0).Kind = SyntaxKind.ColonTrivia
                indexOfFirstColon = 0
                newTrailingTrivia = Nothing
                excessText = triviaList.GetEndOfTrivia(1).Node.ToFullString()
            Else
                For i = 0 To triviaList.Count - 1
                    If triviaList(i).Kind = SyntaxKind.ColonTrivia Then
                        indexOfFirstColon = i
                        Exit For
                    End If
                Next

                newTrailingTrivia = triviaList.GetStartOfTrivia(indexOfFirstColon).Node

                If indexOfFirstColon = triviaList.Count - 1 Then
                    excessText = Nothing
                Else
                    excessText = triviaList.GetEndOfTrivia(indexOfFirstColon + 1).Node.ToFullString()
                    Debug.Assert(triviaList.GetEndOfTrivia(indexOfFirstColon + 1).AnyAndOnly(SyntaxKind.ColonTrivia, SyntaxKind.WhitespaceTrivia))
                End If
            End If

            Dim firstColonTrivia = DirectCast(triviaList(indexOfFirstColon), SyntaxTrivia)

            colonToken = New PunctuationSyntax(SyntaxKind.ColonToken, firstColonTrivia.Text, Nothing, Nothing)

            Return DirectCast(token.WithTrailingTrivia(newTrailingTrivia), SyntaxToken)

        End Function

        Private Function RemoveTrailingColonTriviaAndConvertToColonToken(
                             node As VisualBasicSyntaxNode,
                             <Out> ByRef colonToken As PunctuationSyntax,
                             <Out> ByRef excessText As String
                         ) As VisualBasicSyntaxNode

            Dim lastNonMissing = DirectCast(node.GetLastToken(), SyntaxToken)

            Dim newLastNonMissing = RemoveTrailingColonTriviaAndConvertToColonToken(lastNonMissing, colonToken, excessText)

            Dim newNode = LastTokenReplacer.Replace(node, Function(t) If(t Is lastNonMissing, newLastNonMissing, t))

            ' If no token was replaced we have failed to recover; let high contexts deal with it.
            If newNode Is node Then
                colonToken = Nothing
                excessText = Nothing
            End If

            Return newNode
        End Function

    End Class

End Namespace